////////////////////////////////////////////////////////////////////////////////

#ifndef _CLIENTGUI_H_
#include "clientgui.h"
#endif

////////////////////////////////////////////////////////////////////////////////
       
#ifndef _TIMER_H_
#include "timer.h"
#endif

#ifndef _CLIENT_H_
#include "client.h"
#endif

#ifndef _CLIENTINPUTPACKET_H_
#include "clientinputpacket.h"
#endif

#ifndef _SERVERSTATEPACKET_H_
#include "serverstatepacket.h"
#endif

#ifndef _SCOPEGUARD_H_
#include "scopeguard.h"
#endif

#ifndef _CLIENTMAPPARSER_H_
#include "clientmapparser.h"
#endif

#include <iostream>
#include <cassert>
#include <boost/lexical_cast.hpp>

////////////////////////////////////////////////////////////////////////////////


ClientGUI::ClientGUI()
: m_FirstState( PlayersList(), BulletsList(), WallsList() ),
  m_SecondState( PlayersList(), BulletsList(), WallsList() ),
  m_FirstToSecondCoeff(0.0),
  m_Player(NULL),
  m_Bullet(NULL),
  m_Wall(NULL),
  m_Heart(NULL),
  m_Background(NULL),
  m_Client(NULL),
  m_Stop(false),
  m_ID(-1)
{
    AddEventToObserve(Observer::KEY_INPUT_CHANGE);
    AddEventToObserve(Observer::MOUSE_POSITION_CHANGE);

    m_Input.AddObserver(this);
}


////////////////////////////////////////////////////////////////////////////////


ClientGUI::~ClientGUI()
{
    m_Input.RemoveObserver(this);
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::Start( Client* _client )
{
    assert(_client);
    m_Client = _client;

    RunGameLoop();
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::LoadImages()
{
    // Load player image
    m_Player = m_Graphics.LoadObject("rc/player.png");

    // Load bullet image
    m_Bullet = m_Graphics.LoadObject("rc/bullet.png");

    // Load wall image
    m_Wall = m_Graphics.LoadObject("rc/wall1.png");

    // Load heart image
    m_Heart = m_Graphics.LoadObject("rc/heart.png");

    // Load background image
    m_Background = m_Graphics.LoadObject("rc/background.png");

    if ( !m_Player || !m_Bullet || !m_Wall || !m_Background || !m_Heart )
    {
        assert(0);
        std::cout << "FAIL LOADING IMAGES" << std::endl;
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::CreateMap( const std::string& _mapName )
{
    LoadImages();

    ClientMapParser mapParser( _mapName.c_str() );
    mapParser.Parse();

    const Points& points = mapParser.GetWalls();

    for ( size_t i = 0; i < points.size(); ++i )
        m_Graphics.DrawObject(m_Wall, static_cast<size_t>(points[i].x), static_cast<size_t>(points[i].y), true, m_Background);
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::Render()
{
    // Lock render queue to prevent adding new states from server for a while
    m_RenderQueue.Lock();

    // Fetch states
    bool canIntegrate = FetchTwoStatesAndCoeff();

    // Unlock queue before rendering, because we have already copied info
    // to first and second state and we do not need queue any more.
    // And because rendering can take a lot of time.
    m_RenderQueue.Unlock();


    // If two states taken successfully
    if (canIntegrate)
    {
        // Integrate and put intermediate state into battlefield
        m_Battlefield.Integrate(m_FirstState, m_SecondState, m_FirstToSecondCoeff);

        // Render intermediate state
        RenderBattlefield();
    }
}


////////////////////////////////////////////////////////////////////////////////


bool ClientGUI::FetchTwoStatesAndCoeff()
{
    // This function works correctly only if difference between 2 renderings
    // is less than difference between 2 states, e.g. each state is rendered
    // several times.


    // Number of packets with state to render
    size_t queueSize = m_RenderQueue.size();

    if ( queueSize < 2 )
        // Less than 2 packets in queue - it's not enough to render
        return false;

    // Render speed multiplier - IT IS VERY IMPORTANT parameter - do not remove it!
    // Without speed multiplier client buffers more and more packets,
    // drawing them slowly (seems like with a big delay)
    // Normal render speed is when there is 5 packets in queue
    double speedMultiplier = queueSize * 0.5; // now it is 2 packets


    // Previous packet render time
    static size_t prevPacketTime = Timer::GetCurrTime();

    // Current render time
    size_t currTime = Timer::GetCurrTime();

    // Delta time from previous render
    size_t timeFromPrevRender = static_cast<size_t>(
        static_cast<double>(currTime - prevPacketTime) * speedMultiplier );

    // Time of first and second state
    size_t firstServerTime = m_RenderQueue[0].first;
    size_t secondServerTime = m_RenderQueue[1].first;

    // Current server time
    size_t currServerTime = firstServerTime + timeFromPrevRender;

    if (currServerTime <= secondServerTime)
    {
        // We are still between these two states. 
        // Save them.
        m_FirstState = m_RenderQueue[0].second;
        m_SecondState = m_RenderQueue[1].second;

        // Calculate first-to-second coefficient
        m_FirstToSecondCoeff = static_cast<double>(secondServerTime - currServerTime) /
                               static_cast<double>(secondServerTime - firstServerTime);

        // States successfully fetched!
        return true;
    }
    else
    {
        // We are not between these states.
        // Get next 2 states and process them.
        m_RenderQueue.pop_front();

        // Update prev time
        prevPacketTime = currTime;

        return FetchTwoStatesAndCoeff();
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::RenderBattlefield()
{
    // Ensure images are loaded
    if ( !m_Player || !m_Bullet || !m_Wall || !m_Background )
        return;

    // Draw background
    //m_Graphics.DrawObject(m_Background, 0, 0);
    m_Graphics.ClearScreen();

    m_Graphics.DrawObject(m_Background, 0, 0);

    // Draw FPS
    DrawFPS();

    // Get players
    const PlayersList& players = m_Battlefield.GetPlayers();
    for (PlayersList::const_iterator it = players.begin();
        it != players.end(); ++it)
    {
        const Player& player = *it;
        int xPos = static_cast<int>(player.GetPosition().x);
        int yPos = static_cast<int>(player.GetPosition().y);

        m_Graphics.DrawObject(m_Player, xPos, yPos, true);
    }

    // Get bullets
    const BulletsList& bullets = m_Battlefield.GetBullets();
    for (BulletsList::const_iterator it = bullets.begin();
        it != bullets.end(); ++it)
    {
        const Bullet& bullet = *it;
        int xPos = static_cast<int>(bullet.GetPosition().x);
        int yPos = static_cast<int>(bullet.GetPosition().y);

        m_Graphics.DrawObject(m_Bullet, xPos, yPos, true);
    }

    // Get walls
    const WallsList& walls = m_Battlefield.GetWalls();
    for (WallsList::const_iterator it = walls.begin();
        it != walls.end(); ++it)
    {
        const Wall& wall = *it;
        int xPos = static_cast<int>(wall.GetPosition().x);
        int yPos = static_cast<int>(wall.GetPosition().y);

        m_Graphics.DrawObject(m_Wall, xPos, yPos, true);
    }

    // Draw health points
    int health = m_Battlefield.GetHealthPoints(m_ID)/20;
    for ( int i = 0; i < health; i++ )
        m_Graphics.DrawObject(m_Heart, 10+20*i, 10);

    // Update screen
    m_Graphics.Update();
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::AddStateToRenderQueue( const ServerStatePacket& _packet )
{
    // Get packet creation time
    static size_t lastPacketTime = _packet.GetCreationTime();

    // If it is packet from the past - skip it
    if (_packet.GetCreationTime() >= lastPacketTime)
    {
        // Update time
        lastPacketTime = _packet.GetCreationTime();

        // Add packet to queue
        ScopeGuard guard(m_RenderQueue);
        m_RenderQueue.push_back( std::make_pair(_packet.GetCreationTime(), _packet.GetState() ) );
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::RunGameLoop()
{
    while (!m_Stop)
    {
        // Process input
        ProcessInput();

        // Render state
        Render();

        // Delay
        Timer::Sleep(5);
    }
}


////////////////////////////////////////////////////////////////////////////////


const ClientInput& ClientGUI::GetClientInput() const
{
    return m_ClientInput;
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::OnEvent( Event _event, Observable* _observable )
{
    switch (_event)
    {
    case Observer::KEY_INPUT_CHANGE:
        OnKeyChanged();
        break;

    case Observer::MOUSE_POSITION_CHANGE:
        OnMousePositionChanged();   
        break;

    default:
        assert(0);
        break;
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::OnKeyChanged()
{
    Input::Key key = m_Input.GetKey();
    switch (key.m_Code)
    {
    case Input::W:
        m_ClientInput.SetUpPressed(key.m_State == Input::PRESSED);
        break;
    case Input::S:
        m_ClientInput.SetDownPressed(key.m_State == Input::PRESSED);
        break;
    case Input::A:
        m_ClientInput.SetLeftPressed(key.m_State == Input::PRESSED);
        break;
    case Input::D:
        m_ClientInput.SetRightPressed(key.m_State == Input::PRESSED);
        break;
    case Input::MOUSE1:
        m_ClientInput.SetShootPressed(key.m_State == Input::PRESSED);
        break;
    case Input::EXIT:
        m_Stop = true;
        break;

    default:
        assert(0);
        break;
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::OnMousePositionChanged()
{
    Input::MousePosition pos = m_Input.GetMousePosition();
    m_ClientInput.SetMousePosition( Point( static_cast<float>(pos.x),
                                           static_cast<float>(pos.y) ) );
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::ProcessInput()
{
    // Save previous input
    ClientInput prevInput = m_ClientInput;

    // Process input
    m_Input.ProcessInput();

    // Input changed?
    if ( m_ClientInput.InputChanged(prevInput) )
    {
        ClientInputPacket packet(Timer::GetCurrTime(), m_ClientInput);
        m_Client->SendClientInput(packet);
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::DrawFPS()
{
    // Get time difference
    static size_t prevTime = 0;
    size_t currTime = Timer::GetCurrTime();
    size_t timeDiff = currTime - prevTime;
    prevTime = currTime;

    // Get FPS
    size_t fps = 1000 / timeDiff;
    std::string fpsStr = boost::lexical_cast<std::string>(fps);

    // Draw FPS
    m_Graphics.DrawString( fpsStr.c_str() );
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::SetID( int _ID )
{
    m_ID = _ID;
}


////////////////////////////////////////////////////////////////////////////////
